/*-------------------------------------------------------------------------
 *
 * win32stat.c
 *	  使用 GetFileInformationByHandle 的 <sys/stat.h> 函数的替代实现
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/port/win32stat.c
 *
 *-------------------------------------------------------------------------
 */

#ifdef WIN32

#define UMDF_USING_NTSTATUS

#include "c.h"
#include "port/win32ntdll.h"

#include <windows.h>

/*
 * 将 FILETIME 结构转换为 64 位 time_t。
 */
static __time64_t
filetime_to_time(const FILETIME *ft)
{
	ULARGE_INTEGER unified_ft = {0};
	static const uint64 EpochShift = UINT64CONST(116444736000000000);

	unified_ft.LowPart = ft->dwLowDateTime;
	unified_ft.HighPart = ft->dwHighDateTime;

	if (unified_ft.QuadPart < EpochShift)
		return -1;

	unified_ft.QuadPart -= EpochShift;
	unified_ft.QuadPart /= 10 * 1000 * 1000;

	return unified_ft.QuadPart;
}

/*
 * 将 WIN32 文件属性转换为 Unix 风格的模式。
 *
 * 仅设置所有者权限。
 */
static unsigned short
fileattr_to_unixmode(int attr)
{
	unsigned short uxmode = 0;

	uxmode |= (unsigned short) ((attr & FILE_ATTRIBUTE_DIRECTORY) ?
								(_S_IFDIR) : (_S_IFREG));

	uxmode |= (unsigned short) ((attr & FILE_ATTRIBUTE_READONLY) ?
								(_S_IREAD) : (_S_IREAD | _S_IWRITE));

	/* 不需要使用 CMD 的 PATHEXT 扩展模拟 _S_IEXEC */
	uxmode |= _S_IEXEC;

	return uxmode;
}

/*
 * 将 WIN32 文件信息（来自 HANDLE）转换为 struct stat。
 */
static int fileinfo_to_stat(HANDLE hFile, struct stat *buf)
{
	BY_HANDLE_FILE_INFORMATION fiData;

	memset(buf, 0, sizeof(*buf));

	/*
	 * GetFileInformationByHandle 最小支持版本：Windows XP 和
	 * Windows Server 2003，因此在我们关心的地方都存在。
	 */
	if (!GetFileInformationByHandle(hFile, &fiData))
	{
		_dosmaperr(GetLastError());
		return -1;
	}

	if (fiData.ftLastWriteTime.dwLowDateTime ||
		fiData.ftLastWriteTime.dwHighDateTime)
		buf->st_mtime = filetime_to_time(&fiData.ftLastWriteTime);

	if (fiData.ftLastAccessTime.dwLowDateTime ||
		fiData.ftLastAccessTime.dwHighDateTime)
		buf->st_atime = filetime_to_time(&fiData.ftLastAccessTime);
	else
		buf->st_atime = buf->st_mtime;

	if (fiData.ftCreationTime.dwLowDateTime ||
		fiData.ftCreationTime.dwHighDateTime)
		buf->st_ctime = filetime_to_time(&fiData.ftCreationTime);
	else
		buf->st_ctime = buf->st_mtime;

	buf->st_mode = fileattr_to_unixmode(fiData.dwFileAttributes);
	buf->st_nlink = fiData.nNumberOfLinks;

	buf->st_size = ((((uint64) fiData.nFileSizeHigh) << 32) |
					fiData.nFileSizeLow);

	return 0;
}

/*
 * Windows 实现的 lstat()。
 */
int _pglstat64(const char *name, struct stat *buf)
{
	/*
	 * 我们的 open 封装将 STATUS_DELETE_PENDING 报告为 ENOENT。我们
	 * 请求 FILE_FLAG_BACKUP_SEMANTICS，以便我们也可以打开目录，
	 * 用于有限的目的。我们使用私有句柄版本，因此我们
	 * 不会冒着用尽文件描述符的风险。
	 */
	HANDLE		hFile;
	int			ret;

	hFile = pgwin32_open_handle(name, O_RDONLY, true);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		if (errno == ENOENT)
		{
			/*
			 * 如果这是指向不存在路径的连接点，我们将在这里
			 * 遇到 ENOENT（因为 pgwin32_open_handle 不使用
			 * FILE_FLAG_OPEN_REPARSE_POINT）。在这种情况下，我们将尝试
			 * 使用下面的 readlink()，它将区分真实的 ENOENT 和
			 * 伪符号链接。
			 */
			memset(buf, 0, sizeof(*buf));
			ret = 0;
		}
		else
			return -1;
	}
	else
		ret = fileinfo_to_stat(hFile, buf);

	/*
	 * 连接点在 fileinfo_to_stat() 中表现为目录，因此我们
	 * 需要做更多工作来区分它们。
	 */
	if ((ret == 0 && S_ISDIR(buf->st_mode)) || hFile == INVALID_HANDLE_VALUE)
	{
		char		next[MAXPGPATH];
		ssize_t		size;

		/*
		 * POSIX 规定我们需要将目标路径的长度放入
		 * st_size。使用 readlink() 获取它，或者得知这不是一个
		 * 连接点。
		 */
		size = readlink(name, next, sizeof(next));
		if (size < 0)
		{
			if (errno == EACCES &&
				pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
			{
				/* 在我们下面未链接。 */
				errno = ENOENT;
				ret = -1;
			}
			else if (errno == EINVAL)
			{
				/* 这不是一个连接点，无需处理。 */
			}
			else
			{
				/* 其他一些失败。 */
				ret = -1;
			}
		}
		else
		{
			/* 这是一个连接点，因此将其报告为符号链接。 */
			buf->st_mode &= ~S_IFDIR;
			buf->st_mode |= S_IFLNK;
			buf->st_size = size;
			ret = 0;
		}
	}

	if (hFile != INVALID_HANDLE_VALUE)
		CloseHandle(hFile);
	return ret;
}

/*
 * Windows 实现的 stat()。
 */
int _pgstat64(const char *name, struct stat *buf)
{
	int			loops = 0;
	int			ret;
	char		curr[MAXPGPATH];

	ret = _pglstat64(name, buf);

	strlcpy(curr, name, MAXPGPATH);

	/* 我们需要跟随符号链接（连接点）吗？ */
	while (ret == 0 && S_ISLNK(buf->st_mode))
	{
		char		next[MAXPGPATH];
		ssize_t		size;

		if (++loops > 8)
		{
			errno = ELOOP;
			return -1;
		}

		/*
		 * _pglstat64() 已经调用一次 readlink() 以便填充
		 * st_size，现在我们需要再次调用以获取需要跟随的路径。
		 * 这可以优化，但符号链接上的 stat() 可能比较少见，
		 * 这样做简单。
		 */
		size = readlink(curr, next, sizeof(next));
		if (size < 0)
		{
			if (errno == EACCES &&
				pg_RtlGetLastNtStatus() == STATUS_DELETE_PENDING)
			{
				/* 在我们下面未链接。 */
				errno = ENOENT;
			}
			return -1;
		}
		if (size >= sizeof(next))
		{
			errno = ENAMETOOLONG;
			return -1;
		}
		next[size] = 0;

		ret = _pglstat64(next, buf);
		strcpy(curr, next);
	}

	return ret;
}

/*
 * Windows 实现的 fstat()。
 */
int _pgfstat64(int fileno, struct stat *buf)
{
	HANDLE		hFile = (HANDLE) _get_osfhandle(fileno);
	DWORD		fileType = FILE_TYPE_UNKNOWN;
	unsigned short st_mode;

	if (buf == NULL)
	{
		errno = EINVAL;
		return -1;
	}

	fileType = pgwin32_get_file_type(hFile);
	if (errno != 0)
		return -1;

	switch (fileType)
	{
			/* 指定的文件是一个磁盘文件 */
		case FILE_TYPE_DISK:
			return fileinfo_to_stat(hFile, buf);

			/*
			 * 指定的文件是一个套接字、一个命名管道或一个匿名
			 * 管道。
			 */
		case FILE_TYPE_PIPE:
			st_mode = _S_IFIFO;
			break;
			/* 指定的文件是一个字符文件 */
		case FILE_TYPE_CHAR:
			st_mode = _S_IFCHR;
			break;
			/* 未使用的标志和未知文件类型 */
		case FILE_TYPE_REMOTE:
		case FILE_TYPE_UNKNOWN:
		default:
			errno = EINVAL;
			return -1;
	}

	memset(buf, 0, sizeof(*buf));
	buf->st_mode = st_mode;
	buf->st_dev = fileno;
	buf->st_rdev = fileno;
	buf->st_nlink = 1;
	return 0;
}

#endif							/* WIN32 */
